package com.wangjiacai.utils.web.validator;

import com.wangjiacai.utils.enums.ResultEnum;
import com.wangjiacai.utils.utils.JoinPointUtil;
import org.apache.catalina.connector.RequestFacade;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/*
 * @auther: wangjiacai
 *
 * @create: 2019-01-18 14:00:55
 */
@Aspect
@Component
public class ValidAspect {

    private Map<Class,SecureValid.ErrorMessage> errorMessageMap=new ConcurrentHashMap<>();

    private Validator validator = Validation.buildDefaultValidatorFactory()
            .getValidator();

    @Pointcut("@within(com.wangjiacai.utils.web.validator.SecureValidPointCut)" +
            "|| @annotation(com.wangjiacai.utils.web.validator.SecureValidPointCut)")
    public void secureValid() {
    }

    @Before(value = "secureValid()")
    public void secureValidBefore(JoinPoint joinPoint) {
        Object[] params = joinPoint.getArgs();
        Class[] parameterType = getParameterType(params);
        Method targetMethod = JoinPointUtil.getJoinPointMethod(joinPoint, parameterType);
        if (targetMethod == null) return;
        Annotation[][] annotations = targetMethod.getParameterAnnotations();
        for (int i = 0; i < params.length; i++) {
            Annotation[] paramAnnotations = annotations[i];
            for (Annotation paramAnnotation : paramAnnotations) {
                if (paramAnnotation instanceof SecureValid) {
                    Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(params[i]);
                    if (constraintViolationSet == null || constraintViolationSet.size() == 0) {
                        continue;
                    }
                    BindingResult bindingResult = (BindingResult) params[++i];
                    constraintViolationSet.forEach((objectConstraintViolation -> {
                        ResultEnum resultEnum = getAnnotationResultEnum(objectConstraintViolation.getConstraintDescriptor().getAnnotation());
                        if (resultEnum != null) {
                            bindingResult.rejectValue(objectConstraintViolation.getPropertyPath().toString(),
                                    String.valueOf(resultEnum.getCode()), resultEnum.getMessage());
                        }else{
                            bindingResult.rejectValue(objectConstraintViolation.getPropertyPath().toString(),
                                    String.valueOf(ResultEnum.ParamsMissing.getCode()),
                                    ResultEnum.ParamsMissing.getMessage());
                        }
                    }));
                }
            }
        }
    }

    private ResultEnum getAnnotationResultEnum(Annotation annotation) {
        SecureValid secureValid=annotation.annotationType().getAnnotation(SecureValid.class);
        if(secureValid!=null){
            try {
                Class errorMessageClass = secureValid.errorMessageBy();
                SecureValid.ErrorMessage errorMessage = null;
                if (errorMessageMap.containsKey(errorMessageClass)) {
                    errorMessage = errorMessageMap.get(errorMessageClass);
                } else {
                    Constructor<SecureValid.ErrorMessage> errorMessageConstructor = errorMessageClass.getConstructor();
                    errorMessage = errorMessageConstructor.newInstance();
                    errorMessageMap.put(errorMessageClass, errorMessage);
                }
                return errorMessage.resultEnum(annotation);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private Class[] getParameterType(Object[] params) {
        Class[] parameterType = new Class[params.length];
        for (int i = 0; i < params.length; i++) {
            if (params[i].getClass() == BeanPropertyBindingResult.class) {
                parameterType[i] = BindingResult.class;
            } else if (params[i].getClass() == RequestFacade.class) {
                parameterType[i] = HttpServletRequest.class;
            } else {
                parameterType[i] = params[i].getClass();
            }
        }
        return parameterType;
    }

}
